#include <Arduino.h>
#include <time.h>

#include "SimpleCLI.h"
#include <ArduinoRS485.h>
#include <LT8920.h>
#include "Rtc_Pcf8563.h"
#include "ChiNRF.h"
// #include <retain.h>
// #include "Timer.h"

using namespace simplecli;
RS485Class Rs4854(PC10, PC11, -1, -1);
RS485Class Rs4855(PC12, PD2, -1, -1);
SPIClass spi;
LT8920 lt8920(spi, PB12, PC7, PC6);
ChiNRF xn297(chip_XN297);
int tmr_8920, tmr_297;
uint8_t buf_8920[64], buf_297[64], len_8920, len_297;
TwoWire IIC1;
Rtc_Pcf8563 pcf8563(&IIC1);
#define LED_WIFI PB1
#define LED_RF PB0
#define LED_485 PB8
#define LED_485_2 PB9
#define LED_232_M PC14
#define LED_232_S PA1
SimpleCLI *cli;
HardwareSerial *curSerial;
int rx_lt8920 = 0, tx_lt8920 = 0;
int rx_xn297 = 0, tx_xn297 = 0;
struct
{
  char channels[8];
  int Serial1_baud, Serial1_config;
  int Serial2_baud, Serial2_config;
  int Serial3_baud, Serial3_config;
  int Serial4_baud, Serial4_config;
  int Serial5_baud, Serial5_config;
  int wt8920, wt297;
  u_int16_t lt8920_channel;
  uint64_t xn297_txaddr, xn297_P0, xn297_P1, xn297_P2, xn297_P3, xn297_P4, xn297_P5;
} retain;
// retain_EEPROM rem((uint16 *)&retain, sizeof(retain));
class atcmd
{
public:
  atcmd()
  {
    running = 0;
    last = 0;
  }
  int input(char c)
  {
    if (((last == 'A') || (last == 'a')) && ((c == 'T') || (c == 't')))
    {
      running = 1;
      buf[0] = 'A';
      index = 1;
    }
    if (running)
    {
      buf[index] = c;
      index++;
      if (c == '\n')
        return 1;
    }
    last = c;
    return 0;
  }
  char *get()
  {
    index = 0;
    running = 0;
    return buf;
  }

private:
  char buf[64];
  int index;
  char running;
  char last;
};
atcmd atserial1, atserial2, atserial3, atRs4854, atRs4855, atrf1;
#include <ELClient.h>
#include <ELClientCmd.h>
#include <ELClientMqtt.h>
#include <ELClientSocket.h>
// Initialize a connection to esp-link using the normal hardware serial port both for
// SLIP and for debug messages.
ELClient esp(&Serial1, &Serial1);

// Initialize CMD client (for GetTime)
ELClientCmd elcmd(&esp);

// Initialize the MQTT client
ELClientMqtt mqtt(&esp);

ELClientSocket ntpUDP(&esp);
bool connected;
// Callback made from esp-link to notify of wifi status changes
// Here we just print something out for grins
void wifiCb(void *response)
{
  ELClientResponse *res = (ELClientResponse *)response;
  if (res->argc() == 1)
  {
    uint8_t status;
    res->popArg(&status, 1);

    if (status == STATION_GOT_IP)
    {
      Serial1.println("WIFI CONNECTED");
      connected = true;
    }
    else
    {
      Serial1.print("WIFI NOT READY: ");
      Serial1.println(status);
    }
  }
}

// Callback when MQTT is connected
void mqttConnected(void *response)
{
  Serial1.println("MQTT connected!");
  mqtt.subscribe("/esp-link/1");
  mqtt.subscribe("/hello/world/#");
  //mqtt.subscribe("/esp-link/2", 1);
  //mqtt.publish("/esp-link/0", "test1");
}

// Callback when MQTT is disconnected
void mqttDisconnected(void *response)
{
  Serial1.println("MQTT disconnected");
  connected = false;
}

// Callback when an MQTT message arrives for one of our subscriptions
void mqttData(void *response)
{
  ELClientResponse *res = (ELClientResponse *)response;

  Serial1.print("Received: topic=");
  String topic = res->popString();
  Serial1.println(topic);

  Serial1.print("data=");
  String data = res->popString();
  Serial1.println(data);
}

void mqttPublished(void *response)
{
  Serial1.println("MQTT published");
}
void cbSend8920()
{
  lt8920.sendPacket(buf_8920, len_8920);
  len_8920 = 0;
  tx_lt8920++;
  lt8920.startListening();
}
void cbSend297()
{
  xn297.writeTxPayload(buf_297, len_297);
  len_297 = 0;
}
void sendchannel(int s, int c)
{
  int channel = retain.channels[s];
  if (retain.channels[0] == channel && s != 0)
    Serial1.write(c);
  if (retain.channels[1] == channel && s != 1)
    Serial2.write(c);
  if (retain.channels[2] == channel && s != 2)
    Serial3.write(c);
  if (retain.channels[3] == channel && s != 3)
  {
    Rs4854.noReceive();
    Rs4854.write(c);
    Rs4854.receive();
  }
  if (retain.channels[4] == channel && s != 4)
  {
    Rs4855.noReceive();
    Rs4855.write(c);
    Rs4855.receive();
  }
  if (retain.channels[5] == channel && s != 5)
  {
    buf_8920[len_8920] = c;
    len_8920++;
    tmr_8920 = millis();
  }
  if (retain.channels[6] == channel && s != 6)
    buf_297[len_297] = c;
  len_297++;
  tmr_297 = millis();
}

void info()
{
  char buf[64];
  curSerial->print(pcf8563.formatDate());
  curSerial->println(pcf8563.formatTime());
  sprintf(buf, "COM1: %d , %d , ch=%d\n", retain.Serial1_baud, retain.Serial1_config, retain.channels[0]);
  curSerial->println(buf);
  sprintf(buf, "COM2: %d , %d , ch=%d\n", retain.Serial2_baud, retain.Serial2_config, retain.channels[1]);
  curSerial->println(buf);
  sprintf(buf, "COM3: %d , %d , ch=%d\n", retain.Serial3_baud, retain.Serial3_config, retain.channels[2]);
  curSerial->println(buf);
  sprintf(buf, "COM4: %d , %d , ch=%d\n", retain.Serial4_baud, retain.Serial4_config, retain.channels[3]);
  curSerial->println(buf);
  sprintf(buf, "COM5: %d , %d , ch=%d\n", retain.Serial5_baud, retain.Serial5_config, retain.channels[4]);
  curSerial->println(buf);
  sprintf(buf, "RF1: id=0x%x , freq=%d ,wait=%dms, ch=%d , rx=%d , tx=%d", lt8920.id1, lt8920.getChannel() + 2400, retain.wt8920, retain.channels[5], rx_lt8920, tx_lt8920);
  curSerial->println(buf);
  lt8920.whatsUp(*curSerial);
  uint64_t tx, p0, p1, p2, p3, p4, p5;
  xn297.readRegister(0xa, (uint8_t *)&p0, 5);
  xn297.readRegister(0xb, (uint8_t *)&p1, 5);
  xn297.readRegister(0xc, (uint8_t *)&p2, 5);
  xn297.readRegister(0xd, (uint8_t *)&p3, 5);
  xn297.readRegister(0xe, (uint8_t *)&p4, 5);
  xn297.readRegister(0xf, (uint8_t *)&p5, 5);
  xn297.readRegister(0x10, (uint8_t *)&tx, 5);

  sprintf(buf, "RF2: freq=%d , tx_addr=0x%x ", xn297.readRegister(0x05) + 2400, tx);
  curSerial->println(buf);
  sprintf(buf, "P0=0x%x ", p0);
  curSerial->println(buf);
  sprintf(buf, "P1=0x%x ", p1);
  curSerial->println(buf);
  sprintf(buf, "P2=0x%x ", p2);
  curSerial->println(buf);
  sprintf(buf, "P3=0x%x ", p3);
  curSerial->println(buf);
  sprintf(buf, "P4=0x%x ", p4);
  curSerial->println(buf);
  sprintf(buf, "P5=0x%x ", p5);
  curSerial->println(buf);
}
void load_default()
{
  for (int i = 0; i < 8; i++)
    retain.channels[i] = 0;
  retain.Serial1_baud = 115200;
  retain.Serial1_config = SERIAL_8E1;
  retain.Serial2_baud = 9600;
  retain.Serial2_config = SERIAL_8N1;
  retain.Serial3_baud = 9600;
  retain.Serial3_config = SERIAL_8N1;
  retain.Serial4_baud = 9600;
  retain.Serial4_config = SERIAL_8N1;
  retain.Serial5_baud = 9600;
  retain.Serial5_config = SERIAL_8N1;
  retain.wt297 = 20;
  retain.wt8920 = 20;
  retain.lt8920_channel = 50;
}
void rxcb()
{
  uint8_t buf[255];
  int l = lt8920.read(buf, 255);
  if (l > 0)
  {
    for (int i = 0; i < l; i++)
      sendchannel(5, buf[i]);
    rx_lt8920++;
  }
  lt8920.startListening();
}
void xn297_rxcb()
{
  uint8_t buf[255];
  uint8_t len = xn297.readRegister(0x60);
  xn297.readRxPayload(buf, len);
  for (int i = 0; i < len; i++)
  {
    sendchannel(6, buf[i]);
  }
  rx_xn297++;
}

void setup()
{
  Serial3.begin(9600);
  // SerialUSB.begin();
  // if (rem.status != rem.load_ok)
  {
    load_default();
  }
  Serial1.begin(retain.Serial1_baud, retain.Serial1_config); //WIFI
  Serial2.begin(retain.Serial2_baud, retain.Serial2_config); //232Master
  Serial3.begin(retain.Serial3_baud, retain.Serial3_config); //232 slave
  Rs4854.begin(retain.Serial4_baud, retain.Serial4_config);  //485-1
  Rs4855.begin(retain.Serial5_baud, retain.Serial5_config);  //485-2

  xn297.setOnReceive(xn297_rxcb);
  xn297.begin(PC5, PA6, PA7, PA5, PA4, PC4);
  lt8920.begin();
  xn297.disguiseAs(chip_XN297);
  xn297.setRxPayloadWidth(0, 1); // pipe0, payloadLen 1

  xn297.setRxAddressP0(retain.xn297_P0);
  xn297.setRxAddressP1(retain.xn297_P1);
  xn297.setRxAddressP2(retain.xn297_P2);
  xn297.setRxAddressP3(retain.xn297_P3);
  xn297.setRxAddressP4(retain.xn297_P4);
  xn297.setRxAddressP5(retain.xn297_P5);
  xn297.setTxAddress(retain.xn297_txaddr);
  if (lt8920.id1 != 0xf413)
  {
    Serial3.println("lt8920 error!!!");
  }
  // lt8920.setCurrentControl(0xf, 0xf);
  lt8920.setDataRate(LT8920::LT8920_62KBPS);
  lt8920.setChannel(retain.lt8920_channel);
  // while (1){
  // //   // Serial3.println("send");
  //   lt8920.sendPacket((uint8_t *)"01234567890123456789", 20);
  // }
  tmr_8920 = 0;

  pinMode(LED_WIFI, OUTPUT);
  pinMode(LED_RF, OUTPUT);
  pinMode(LED_485, OUTPUT);
  pinMode(LED_485_2, OUTPUT);
  pinMode(LED_232_M, OUTPUT);
  pinMode(LED_232_S, OUTPUT);
  // =========== Create CommandParser =========== //
  cli = new SimpleCLI();

  // when no valid command could be found for given user input
  cli->onNotFound = [](String str) {
    Serial1.println("\"" + str + "\" not found");
  };
  // ============================================ //

  // =========== Add ats command =========== //
  Command *atd = new Command("ATD", [](Cmd *cmd) {
    info();
  });
  Command *atrf = new Command("ATRF", [](Cmd *cmd) {
    int rf = cmd->getValue("p").toInt();
    int channel = cmd->getValue("c").toInt();
    int wt = cmd->getValue("w").toInt();
    if (rf == 1)
    {
      lt8920.setChannel(channel);
      retain.wt8920 = wt;
    }
    else if (rf == 2)
    {
      xn297.setFreq(channel);
      retain.wt297 = wt;
    }
    else
    {
      curSerial->println("name error.");
      return;
    }
    curSerial->println("OK.");
  });
  atrf->addArg(new OptArg("p", ""));
  atrf->addArg(new OptArg("c", "50"));
  atrf->addArg(new OptArg("w", "20"));
  Command *ping = new Command("ATS", [](Cmd *cmd) {
    String port = cmd->getValue("p");
    int baud = cmd->getValue("b").toInt();
    String format = cmd->getValue("e");
    int channel = cmd->getValue("c").toInt();

    int f = SERIAL_8N1;
    if (format == "8e2" || format == "8E2")
      f = SERIAL_8E2;
    else if (format == "8e1" || format == "8E1")
      f = SERIAL_8E1;
    else if (format == "8N1" || format == "8n1")
      f = SERIAL_8N1;
    else if (format == "8N2" || format == "8n2")
      f = SERIAL_8N2;
    else if (format == "8O1" || format == "8o1")
      f = SERIAL_8O1;
    else if (format == "8O2" || format == "8o2")
      f = SERIAL_8O2;
    else
    {
      curSerial->println("config error.");
      return;
    }
    if (port == "COM1")
    {
      retain.Serial1_baud = baud;
      retain.Serial1_config = f;
      Serial1.begin(baud, f);
      retain.channels[0] = channel;
    }
    else if (port == "COM2")
    {
      retain.Serial2_baud = baud;
      retain.Serial2_config = f;
      Serial2.begin(baud, f);
      retain.channels[1] = channel;
    }
    else if (port == "COM3")
    {
      retain.Serial3_baud = baud;
      retain.Serial3_config = f;
      Serial3.begin(baud, f);
      retain.channels[2] = channel;
    }
    else if (port == "COM4")
    {
      retain.Serial4_baud = baud;
      retain.Serial4_config = f;
      Rs4854.begin(baud, f);
      retain.channels[3] = channel;
    }
    else if (port == "COM5")
    {
      retain.Serial5_baud = baud;
      retain.Serial5_config = f;
      Rs4855.begin(baud, f);
      retain.channels[4] = channel;
    }
    else
    {
      curSerial->println("port name error.");
      return;
    }
    curSerial->println("setup ok.");
  });
  ping->addArg(new OptArg("p", ""));
  ping->addArg(new OptArg("b", "115200"));
  ping->addArg(new OptArg("e", "8e1"));
  ping->addArg(new OptArg("c", "0"));
  cli->addCmd(ping);
  cli->addCmd(atd);
  cli->addCmd(atrf);
  // ======================================== //

  // run tests
  // cli->parse("ats");
  // cli->parse("ats -p COM2 -b 9600 -e 8e1 -c 1");

  curSerial = &Serial1;
  info();
  curSerial = &Serial2;
  info();
  curSerial = &Serial3;
  info();
  // curSerial = &Rs4854;
  // info();
  // curSerial = &Rs4855;
  // info();

  // lt8920.setRxCb(rxcb);
  lt8920.startListening();
  esp.wifiCb.attach(wifiCb); // wifi status change callback, optional (delete if not desired)
  bool ok;
  do
  {
    ok = esp.Sync(); // sync up with esp-link, blocks for up to 2 seconds
    if (!ok)
      Serial1.println("EL-Client sync failed!");
  } while (!ok);
  Serial1.println("EL-Client synced!");

  // Set-up callbacks for events and initialize with es-link.
  mqtt.connectedCb.attach(mqttConnected);
  mqtt.disconnectedCb.attach(mqttDisconnected);
  mqtt.publishedCb.attach(mqttPublished);
  mqtt.dataCb.attach(mqttData);
  mqtt.setup();
}
static int count;
static uint32_t last;
void loop()
{
  static int step = 0;
  esp.Process();

  if (connected && (millis() - last) > 4000)
  {
    Serial1.println("publishing");
    char buf[12];

    itoa(count++, buf, 10);
    mqtt.publish("/esp-link/1", buf);

    itoa(count + 99, buf, 10);
    mqtt.publish("/hello/world/arduino", buf);

    uint32_t t = elcmd.GetTime();
    Serial3.print("Time: ");
    Serial3.println(t);
    if (t > 99999999)
    {
      struct tm *ct = gmtime((time_t *)&t);
      pcf8563.setDateTime(ct->tm_yday, ct->tm_wday, ct->tm_mon, 0, ct->tm_year, ct->tm_hour, ct->tm_min, ct->tm_sec);
    }
    last = millis();
  }
  // if (SerialUSB.isConnected())
  // {
  //   digitalWrite(LED_RF, 1);
  // }
  // if (SerialUSB.isConnected() && (SerialUSB.getDTR() || SerialUSB.getRTS()))
  // {
  //   for (int i = 0; i < 10; i++)
  //   {
  //     SerialUSB.println(123456, BIN);
  //   }
  // }
  if (Serial1.available())
  {
    int c = Serial1.read();
    if (atserial1.input(c))
    {
      curSerial = &Serial1;
      Serial3.print("input:");
      Serial3.println(atserial1.get());
      cli->parse(atserial1.get());
    }
    sendchannel(0, c);
  }
  if (Serial2.available())
  {
    int c = Serial2.read();
    if (atserial2.input(c))
    {
      curSerial = &Serial2;
      cli->parse(atserial2.get());
    }
    sendchannel(1, c);
  }
  if (Serial3.available())
  {
    int c = Serial3.read();
    if (atserial3.input(c))
    {
      curSerial = &Serial3;

      cli->parse(atserial3.get());
    }
    // sendchannel(2, c);
  }
  if (Rs4854.available())
  {
    int c = Rs4854.read();
    if (atRs4854.input(c))
    {
      curSerial = &Rs4854;

      cli->parse(atRs4854.get());
    }
    sendchannel(3, c);
  }
  if (Rs4855.available())
  {
    int c = Rs4855.read();
    if (atRs4855.input(c))
    {
      curSerial = &Rs4855;

      cli->parse(atRs4855.get());
    }
    sendchannel(4, c);
  }

  switch (step)
  {
  case 5:
    step = 0;
    break;
  default:
    step++;
  }
  digitalWrite(LED_WIFI, step == 0);
  digitalWrite(LED_RF, step == 1);
  digitalWrite(LED_485, step == 2);
  digitalWrite(LED_485_2, step == 3);
  digitalWrite(LED_232_M, step == 4);
  digitalWrite(LED_232_S, step == 5);
  if (tmr_297 && millis() - tmr_297 > retain.wt297)
  {
    cbSend297();
    tmr_297 = 0;
  }
  if (tmr_8920 && millis() - tmr_8920 > retain.wt8920)
  {
    cbSend8920();
    tmr_8920 = 0;
  }
  // rem.loop();
}